package com.tk.mygenerate;

import java.math.BigDecimal;
import java.sql.Timestamp;
import java.sql.Types;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import org.mybatis.generator.api.IntrospectedColumn;
import org.mybatis.generator.api.JavaTypeResolver;
import org.mybatis.generator.api.dom.java.FullyQualifiedJavaType;
import org.mybatis.generator.config.Context;
import org.mybatis.generator.config.PropertyRegistry;
import org.mybatis.generator.internal.types.JavaTypeResolverDefaultImpl;
import org.mybatis.generator.internal.util.StringUtility;

public class MyJavaTypeResolverDefaultImpl implements JavaTypeResolver {

  protected List<String> warnings;

  protected Properties properties;

  protected Context context;

  protected boolean forceBigDecimals;
  protected boolean useJSR310Types;

  protected Map<Integer, JavaTypeResolverDefaultImpl.JdbcTypeInformation> typeMap;

  // TODO - remove when we get to JDK 8
  private static final int TIME_WITH_TIMEZONE = 2013;
  private static final int TIMESTAMP_WITH_TIMEZONE = 2014;

  public MyJavaTypeResolverDefaultImpl() {
    super();
    properties = new Properties();
    typeMap = new HashMap<Integer, JavaTypeResolverDefaultImpl.JdbcTypeInformation>();

    typeMap
        .put(Types.ARRAY, new JavaTypeResolverDefaultImpl.JdbcTypeInformation("ARRAY", //$NON-NLS-1$
            new FullyQualifiedJavaType(Object.class.getName())));
    typeMap.put(Types.BIGINT,
        new JavaTypeResolverDefaultImpl.JdbcTypeInformation("BIGINT", //$NON-NLS-1$
            new FullyQualifiedJavaType(Long.class.getName())));
    typeMap.put(Types.BINARY,
        new JavaTypeResolverDefaultImpl.JdbcTypeInformation("BINARY", //$NON-NLS-1$
            new FullyQualifiedJavaType("byte[]"))); //$NON-NLS-1$
    typeMap.put(Types.BIT, new JavaTypeResolverDefaultImpl.JdbcTypeInformation("BIT", //$NON-NLS-1$
        new FullyQualifiedJavaType(Boolean.class.getName())));
    typeMap
        .put(Types.BLOB, new JavaTypeResolverDefaultImpl.JdbcTypeInformation("BLOB", //$NON-NLS-1$
            new FullyQualifiedJavaType("byte[]"))); //$NON-NLS-1$
    typeMap.put(Types.BOOLEAN,
        new JavaTypeResolverDefaultImpl.JdbcTypeInformation("BOOLEAN", //$NON-NLS-1$
            new FullyQualifiedJavaType(Boolean.class.getName())));
    typeMap
        .put(Types.CHAR, new JavaTypeResolverDefaultImpl.JdbcTypeInformation("CHAR", //$NON-NLS-1$
            new FullyQualifiedJavaType(String.class.getName())));
    typeMap
        .put(Types.CLOB, new JavaTypeResolverDefaultImpl.JdbcTypeInformation("CLOB", //$NON-NLS-1$
            new FullyQualifiedJavaType(String.class.getName())));
    typeMap.put(Types.DATALINK,
        new JavaTypeResolverDefaultImpl.JdbcTypeInformation("DATALINK", //$NON-NLS-1$
            new FullyQualifiedJavaType(Object.class.getName())));
    typeMap
        .put(Types.DATE, new JavaTypeResolverDefaultImpl.JdbcTypeInformation("DATE", //$NON-NLS-1$
            new FullyQualifiedJavaType(Date.class.getName())));
    typeMap.put(Types.DECIMAL,
        new JavaTypeResolverDefaultImpl.JdbcTypeInformation("DECIMAL", //$NON-NLS-1$
            new FullyQualifiedJavaType(BigDecimal.class.getName())));
    typeMap.put(Types.DISTINCT,
        new JavaTypeResolverDefaultImpl.JdbcTypeInformation("DISTINCT", //$NON-NLS-1$
            new FullyQualifiedJavaType(Object.class.getName())));
    typeMap.put(Types.DOUBLE,
        new JavaTypeResolverDefaultImpl.JdbcTypeInformation("DOUBLE", //$NON-NLS-1$
            new FullyQualifiedJavaType(Double.class.getName())));
    typeMap
        .put(Types.FLOAT, new JavaTypeResolverDefaultImpl.JdbcTypeInformation("FLOAT", //$NON-NLS-1$
            new FullyQualifiedJavaType(Double.class.getName())));
    typeMap.put(Types.INTEGER,
        new JavaTypeResolverDefaultImpl.JdbcTypeInformation("INTEGER", //$NON-NLS-1$
            new FullyQualifiedJavaType(Integer.class.getName())));
    typeMap.put(Types.JAVA_OBJECT,
        new JavaTypeResolverDefaultImpl.JdbcTypeInformation("JAVA_OBJECT", //$NON-NLS-1$
            new FullyQualifiedJavaType(Object.class.getName())));
    typeMap.put(Types.LONGNVARCHAR,
        new JavaTypeResolverDefaultImpl.JdbcTypeInformation("LONGNVARCHAR", //$NON-NLS-1$
            new FullyQualifiedJavaType(String.class.getName())));
    typeMap.put(Types.LONGVARBINARY, new JavaTypeResolverDefaultImpl.JdbcTypeInformation(
        "LONGVARBINARY", //$NON-NLS-1$
        new FullyQualifiedJavaType("byte[]"))); //$NON-NLS-1$
    typeMap.put(Types.LONGVARCHAR,
        new JavaTypeResolverDefaultImpl.JdbcTypeInformation("LONGVARCHAR", //$NON-NLS-1$
            new FullyQualifiedJavaType(String.class.getName())));
    typeMap
        .put(Types.NCHAR, new JavaTypeResolverDefaultImpl.JdbcTypeInformation("NCHAR", //$NON-NLS-1$
            new FullyQualifiedJavaType(String.class.getName())));
    typeMap
        .put(Types.NCLOB, new JavaTypeResolverDefaultImpl.JdbcTypeInformation("NCLOB", //$NON-NLS-1$
            new FullyQualifiedJavaType(String.class.getName())));
    typeMap.put(Types.NVARCHAR,
        new JavaTypeResolverDefaultImpl.JdbcTypeInformation("NVARCHAR", //$NON-NLS-1$
            new FullyQualifiedJavaType(String.class.getName())));
    typeMap
        .put(Types.NULL, new JavaTypeResolverDefaultImpl.JdbcTypeInformation("NULL", //$NON-NLS-1$
            new FullyQualifiedJavaType(Object.class.getName())));
    typeMap.put(Types.NUMERIC,
        new JavaTypeResolverDefaultImpl.JdbcTypeInformation("NUMERIC", //$NON-NLS-1$
            new FullyQualifiedJavaType(BigDecimal.class.getName())));
    typeMap
        .put(Types.OTHER, new JavaTypeResolverDefaultImpl.JdbcTypeInformation("OTHER", //$NON-NLS-1$
            new FullyQualifiedJavaType(Object.class.getName())));
    typeMap
        .put(Types.REAL, new JavaTypeResolverDefaultImpl.JdbcTypeInformation("REAL", //$NON-NLS-1$
            new FullyQualifiedJavaType(Float.class.getName())));
    typeMap.put(Types.REF, new JavaTypeResolverDefaultImpl.JdbcTypeInformation("REF", //$NON-NLS-1$
        new FullyQualifiedJavaType(Object.class.getName())));
    typeMap.put(Types.SMALLINT,
        new JavaTypeResolverDefaultImpl.JdbcTypeInformation("SMALLINT", //$NON-NLS-1$
            new FullyQualifiedJavaType(Short.class.getName())));
    typeMap.put(Types.STRUCT,
        new JavaTypeResolverDefaultImpl.JdbcTypeInformation("STRUCT", //$NON-NLS-1$
            new FullyQualifiedJavaType(Object.class.getName())));
    typeMap
        .put(Types.TIME, new JavaTypeResolverDefaultImpl.JdbcTypeInformation("TIME", //$NON-NLS-1$
            new FullyQualifiedJavaType(Date.class.getName())));
    typeMap.put(Types.TIMESTAMP,
        new JavaTypeResolverDefaultImpl.JdbcTypeInformation("TIMESTAMP", //$NON-NLS-1$
            new FullyQualifiedJavaType(Timestamp.class.getName())));
    typeMap.put(Types.TINYINT,
        new JavaTypeResolverDefaultImpl.JdbcTypeInformation("TINYINT", //$NON-NLS-1$
            new FullyQualifiedJavaType(Byte.class.getName())));
    typeMap.put(Types.VARBINARY,
        new JavaTypeResolverDefaultImpl.JdbcTypeInformation("VARBINARY", //$NON-NLS-1$
            new FullyQualifiedJavaType("byte[]"))); //$NON-NLS-1$
    typeMap.put(Types.VARCHAR,
        new JavaTypeResolverDefaultImpl.JdbcTypeInformation("VARCHAR", //$NON-NLS-1$
            new FullyQualifiedJavaType(String.class.getName())));
    // JDK 1.8 types
    typeMap.put(TIME_WITH_TIMEZONE,
        new JavaTypeResolverDefaultImpl.JdbcTypeInformation("TIME_WITH_TIMEZONE", //$NON-NLS-1$
            new FullyQualifiedJavaType("java.time.OffsetTime"))); //$NON-NLS-1$
    typeMap.put(TIMESTAMP_WITH_TIMEZONE,
        new JavaTypeResolverDefaultImpl.JdbcTypeInformation("TIMESTAMP_WITH_TIMEZONE", //$NON-NLS-1$
            new FullyQualifiedJavaType("java.time.OffsetDateTime"))); //$NON-NLS-1$
  }

  @Override
  public void addConfigurationProperties(Properties properties) {
    this.properties.putAll(properties);
    forceBigDecimals = StringUtility
        .isTrue(properties
            .getProperty(PropertyRegistry.TYPE_RESOLVER_FORCE_BIG_DECIMALS));
    useJSR310Types = StringUtility
        .isTrue(properties
            .getProperty(PropertyRegistry.TYPE_RESOLVER_USE_JSR310_TYPES));
  }

  @Override
  public FullyQualifiedJavaType calculateJavaType(
      IntrospectedColumn introspectedColumn) {
    FullyQualifiedJavaType answer = null;
    JavaTypeResolverDefaultImpl.JdbcTypeInformation jdbcTypeInformation = typeMap
        .get(introspectedColumn.getJdbcType());

    if (jdbcTypeInformation != null) {
      answer = jdbcTypeInformation.getFullyQualifiedJavaType();
      answer = overrideDefaultType(introspectedColumn, answer);
    }

    return answer;
  }

  protected FullyQualifiedJavaType overrideDefaultType(IntrospectedColumn column,
      FullyQualifiedJavaType defaultType) {
    FullyQualifiedJavaType answer = defaultType;

    switch (column.getJdbcType()) {
      case Types.BIT:
        answer = calculateBitReplacement(column, defaultType);
        break;
      case Types.DATE:
        answer = calculateDateType(column, defaultType);
        break;
      case Types.DECIMAL:
      case Types.NUMERIC:
        answer = calculateBigDecimalReplacement(column, defaultType);
        break;
      case Types.TIME:
        answer = calculateTimeType(column, defaultType);
        break;
      case Types.TIMESTAMP:
        answer = calculateTimestampType(column, defaultType);
        break;
      default:
        break;
    }

    return answer;
  }

  protected FullyQualifiedJavaType calculateDateType(IntrospectedColumn column,
      FullyQualifiedJavaType defaultType) {
    FullyQualifiedJavaType answer;

    if (useJSR310Types) {
      answer = new FullyQualifiedJavaType("java.time.LocalDate"); //$NON-NLS-1$
    } else {
      answer = defaultType;
    }

    return answer;
  }

  protected FullyQualifiedJavaType calculateTimeType(IntrospectedColumn column,
      FullyQualifiedJavaType defaultType) {
    FullyQualifiedJavaType answer;

    if (useJSR310Types) {
      answer = new FullyQualifiedJavaType("java.time.LocalTime"); //$NON-NLS-1$
    } else {
      answer = defaultType;
    }

    return answer;
  }

  protected FullyQualifiedJavaType calculateTimestampType(IntrospectedColumn column,
      FullyQualifiedJavaType defaultType) {
    FullyQualifiedJavaType answer;

    if (useJSR310Types) {
      answer = new FullyQualifiedJavaType("java.time.LocalDateTime"); //$NON-NLS-1$
    } else {
      answer = defaultType;
    }

    return answer;
  }

  protected FullyQualifiedJavaType calculateBitReplacement(IntrospectedColumn column,
      FullyQualifiedJavaType defaultType) {
    FullyQualifiedJavaType answer;

    if (column.getLength() > 1) {
      answer = new FullyQualifiedJavaType("byte[]"); //$NON-NLS-1$
    } else {
      answer = defaultType;
    }

    return answer;
  }

  protected FullyQualifiedJavaType calculateBigDecimalReplacement(IntrospectedColumn column,
      FullyQualifiedJavaType defaultType) {
    FullyQualifiedJavaType answer;

    if (column.getScale() > 0 || column.getLength() > 18 || forceBigDecimals) {
      answer = defaultType;
    } else if (column.getLength() > 9) {
      answer = new FullyQualifiedJavaType(Long.class.getName());
    } else if (column.getLength() > 4) {
      answer = new FullyQualifiedJavaType(Integer.class.getName());
    } else {
      answer = new FullyQualifiedJavaType(Short.class.getName());
    }

    return answer;
  }

  @Override
  public String calculateJdbcTypeName(IntrospectedColumn introspectedColumn) {
    String answer = null;
    JavaTypeResolverDefaultImpl.JdbcTypeInformation jdbcTypeInformation = typeMap
        .get(introspectedColumn.getJdbcType());

    if (jdbcTypeInformation != null) {
      answer = jdbcTypeInformation.getJdbcTypeName();
    }

    return answer;
  }

  @Override
  public void setWarnings(List<String> warnings) {
    this.warnings = warnings;
  }

  @Override
  public void setContext(Context context) {
    this.context = context;
  }

  public static class JdbcTypeInformation {

    private String jdbcTypeName;

    private FullyQualifiedJavaType fullyQualifiedJavaType;

    public JdbcTypeInformation(String jdbcTypeName,
        FullyQualifiedJavaType fullyQualifiedJavaType) {
      this.jdbcTypeName = jdbcTypeName;
      this.fullyQualifiedJavaType = fullyQualifiedJavaType;
    }

    public String getJdbcTypeName() {
      return jdbcTypeName;
    }

    public FullyQualifiedJavaType getFullyQualifiedJavaType() {
      return fullyQualifiedJavaType;
    }
  }
}
